﻿
using System;
using System.Linq;
using StockholmsStad.Data.Context;
using StockholmsStad.Data.ContractFactory;
using StockholmsStad.Data.Interfaces;
using StockholmsStad.Runtime.Serialization.DataContract;
using StockholmsStad.Runtime.Serialization.DataContract.Enums;
using StockholmsStad.Utilities.Exceptions;
using StockholmsStad.Utilities.Enums;
using System.Collections.Generic;

namespace StockholmsStad.Data.DatabaseAgents
{
    internal class UserAgent : DBAgentBase, IUserAgent
    {
        #region Implementation of IUserAgent

        /// <summary>
        /// Skapar en ny användare
        /// </summary>
        /// <param name="context">Det databascontext som operationen köra i</param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="userRoleId"></param>
        /// <returns></returns>
        public int CreateUser(IDataContext context, string username, string password, int userRoleId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                UserFactory factory = FactoryAgent.GetUserFactory();
                
                var entityUser = factory.CreateEntityObject(null, username, password, userRoleId);
                entityUser.Active = true;
                return Insert(entityUser, dbCtx);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public int AddUser(IDataContext context, ParvisUser parvisUser, string password)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                UserFactory factory = FactoryAgent.GetUserFactory();

                var entityUser = factory.CreateEntityObject(parvisUser);
                entityUser.Password = password;
                var id = Insert(entityUser, dbCtx);

                return id;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public void UpdateUser(IDataContext context, ParvisUser parvisUser)
        {
            try
            {
                if (!parvisUser.ParvisUserId.HasValue)
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);

                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                var parvisEntity = dbCtx.ParvisUsers.Where(p => p.ParvisUserId == parvisUser.ParvisUserId.Value).SingleOrDefault();

                if (parvisEntity == null)
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);

                //Entity.ParvisUser parvisEntity;
                if(!parvisEntity.EmailAddress.Equals(parvisUser.Username))
                {
                    parvisEntity.EmailAddress = parvisUser.Username;

                    // Skicka mail om att anändarnamnet har ändrats
                    dbCtx.SendForgottenPasswordMail(parvisEntity.EmailAddress,
                                                    string.Format("{0} {1}", parvisUser.FirstName, parvisUser.LastName),
                                                    parvisUser.Username, parvisUser.Password);
                }

                parvisEntity.FirstName = parvisUser.FirstName;
                parvisEntity.LastName = parvisUser.LastName;
                parvisEntity.CellPhoneNumber = parvisUser.CellPhoneNumber;
                parvisEntity.PhoneNumber = parvisUser.PhoneNumber;
                parvisEntity.IsActive = parvisUser.IsActive;
                parvisEntity.UserRoleId = (int) parvisUser.Role;
                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public void DeleteUser(IDataContext context, int userId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var user = dbCtx.Users.Where(u => u.UserId == userId).SingleOrDefault();
                if (user != null)
                {
                    dbCtx.Users.DeleteOnSubmit(user);
                    dbCtx.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public void DeleteParvisUser(IDataContext context, int userId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var user = dbCtx.ParvisUsers.Where(p => p.ParvisUserId == userId).SingleOrDefault();
                if (user != null)
                {
                    dbCtx.ParvisUsers.DeleteOnSubmit(user);
                    dbCtx.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }
        }

        public User GetUser(IDataContext context, string username)
        {
            try
            {
                const bool isActive = true;
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.User userEntity = dbCtx.Users.
                    Where(u => u.UserName == username && u.Active == isActive).FirstOrDefault();
                if (userEntity == null)
                    return null;

                UserFactory factory = FactoryAgent.GetUserFactory();
                return factory.CreateContractObject(userEntity);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public ParvisUser GetParvisUser(IDataContext context, int parvisUserId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.ParvisUser userEntity =
                    dbCtx.ParvisUsers.Where(p => p.ParvisUserId == parvisUserId && p.IsActive).FirstOrDefault();

                if (userEntity == null)
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);

                var factory = new UserFactory();
                return factory.CreateContractObject(userEntity);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// Hämtar alla användare som inte har rollen kund
        /// </summary>
        /// <param name="context">Det databascontext som operationen köra i.</param>
        /// <param name="includeInactiveUsers">Inkludera även inaktiva användare.</param>
        /// <returns>En lista med alla användare.</returns>
        public List<ParvisUser> GetUsers(IDataContext context, bool includeInactiveUsers)
        {
            try
            {
                var factory = new UserFactory();

                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                return dbCtx.GetParvisUsers(includeInactiveUsers).Select(u => factory.CreateContractObject(u)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        ///<summary>
        /// Kontrollerar att användaren får logga in på webben
        ///</summary>
        ///<param name="context">Det databascontext som operationen köra i</param>
        ///<param name="username"></param>
        ///<param name="password"></param>
        ///<returns></returns>
        public bool IsValidWebUser(IDataContext context, string username, string password)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.User userEntity = (from u in dbCtx.Users
                                          where u.UserName == username &&
                                                u.Password == password &&
                                                u.UserRoleId == (int) UserRole.Customer
                                          select u).FirstOrDefault();
                return userEntity != null;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        ///<summary>
        /// Uppdaterar användarnamnet för user
        ///</summary>
        ///<param name="context">Det databascontext som operationen köra i</param>
        ///<param name="coupleId">Hitta user med hjälp av coupleId</param>
        ///<param name="username"></param>
        public void UpdateUser(IDataContext context, int coupleId, string username)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Ticket ticket = dbCtx.Tickets.Where(t => t.CoupleId == coupleId).FirstOrDefault();

                if (ticket == null || !ticket.UserId.HasValue)
                {
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);
                }

                Entity.User userEntity = (from u in dbCtx.Users
                                          where u.UserId == ticket.UserId.Value
                                          select u).FirstOrDefault();
                if (userEntity != null)
                {
                    userEntity.UserName = username;
                    dbCtx.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// Returnerar true om användarnamnet är unikt
        /// </summary>
        /// <param name="context">Det databascontext som operationen kör i</param>
        /// <param name="username"></param>
        /// <returns></returns>
        public bool IsUserNameUnique(IDataContext context, string username)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var user = dbCtx.Users.Where(u => u.UserName == username).FirstOrDefault();
                return user == null;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        /// <summary>
        /// Genererar ett nytt slumpat lösenord
        /// </summary>
        /// <param name="context">Det databascontext som operationen kör i</param>
        /// <returns></returns>
        public string CreatePassword(IDataContext context)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                string password = null;
                dbCtx.CreatePassword(ref password);
                return password;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        ///<summary>
        /// Skicka mail med lösenord till användaren
        ///</summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="user">Vilken person som ska ha uppgifterna</param>
        public void SendForgottenPasswordMail(IDataContext context, ParvisUser user)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                // Hämta lösenordet
                string password = dbCtx.
                    ParvisUsers.Where(u => u.ParvisUserId == user.ParvisUserId).
                    Select(u => u.Password).FirstOrDefault();
                dbCtx.SendForgottenPasswordMail(user.Username, string.Format("{0} {1}", user.FirstName, user.LastName), user.Username, password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        ///<summary>
        /// Skicka mail med lösenord till användaren
        ///</summary>
        ///<param name="context">Det databascontext som operationen kör i</param>
        ///<param name="person">Vilken person som ska ha uppgifterna</param>
        ///<param name="user">Användaruppgifterna</param>
        public void SendForgottenPasswordMail(IDataContext context, PersonDetails person, User user)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                // Hämta lösenordet
                string password = dbCtx.Users.Where(u => u.UserName == user.Username).Select(u => u.Password).FirstOrDefault();
                dbCtx.SendForgottenPasswordMail(person.Email, person.FullName, user.Username, password);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void AddUser(IDataContext context, string username, int userRoleId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                string password = CreatePassword(context);
                var user = new Entity.User
                {
                    UserName = username,
                    UserRoleId = userRoleId,
                    Password = password,
                    Active = true,
                };

                //dbCtx.SendRegistrationInParvisMail(username, username, password);
                Insert(user, dbCtx);
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex.InnerException);
            }

        }

        public User Login(IDataContext context, User user)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.ParvisUser userEntity = (from u in dbCtx.ParvisUsers
                                          where u.EmailAddress == user.Username &&
                                                u.Password == user.Password &&
                                                u.IsActive
                                          select u).FirstOrDefault();

                if (userEntity != null)
                {
                    user.Role = (UserRole)Enum.ToObject(typeof(UserRole), userEntity.UserRole.UserRoleId);
                 
                    if (userEntity.UserRoleId == (int)UserRole.Customer)
                        return null;

                    user.UserId = userEntity.ParvisUserId;
                    // don't return the password, no need for it anymore
                    user.Password = string.Empty;
                    return user;
                }

                return null;
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void SetUserInactive(IDataContext context, string username)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();

                Entity.ParvisUser user = dbCtx.ParvisUsers.Where(u => u.EmailAddress == username).FirstOrDefault();

                if(user ==  null)
                    throw new StockholmStadSystemException(SystemErrorCode.UserNotExists);

                user.IsActive = false;
                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public List<UserRole> GetRoles(IDataContext context, List<UserRole> excludeRoles)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                var factory = new UserFactory();

                if (excludeRoles != null)
                {
                    var result = from role in dbCtx.UserRoles
                                 where !(from selectedRoles in excludeRoles select (int)selectedRoles).Contains(role.UserRoleId)
                                 orderby role.Name 
                                 select factory.CreateContractObject(role);

                    return result.ToList();
                }

                return dbCtx.UserRoles.OrderBy(u => u.Name).
                    Select(u => factory.CreateContractObject(u)).ToList();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        public void ChangeUser(IDataContext context, int ticketId, int? userId)
        {
            try
            {
                StockholmsStadDataContext dbCtx = context.GetInternalContext();
                Entity.Ticket ticket = dbCtx.Tickets.Where(t => t.TicketId == ticketId).FirstOrDefault();

                //Om ärendet har en user men den har tagits bort
                if(ticket.UserId.HasValue && !userId.HasValue)
                {
                    ticket.UserId = userId;
                }

                //Om ärendet har en user och den har ändrats
                if (ticket.UserId.HasValue && userId.HasValue)
                {
                    if(ticket.UserId.Value != userId.Value)
                    {
                        ticket.UserId = userId;
                    }
                }

                //Om ärendet INTE har en user och den har skapats
                if (!ticket.UserId.HasValue && userId.HasValue)
                {
                    ticket.UserId = userId;
                }

                dbCtx.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw new StockholmStadSystemException(SystemErrorCode.DataAccessFailure, ex);
            }
        }

        #endregion
    }
}